package com.zmx.gateway.filter;

import com.zmx.gateway.config.MemberBlacklistCacheConfig;
import com.zmx.gateway.dto.ResponseResult;
import com.zmx.gateway.properties.MallAuthProperties;
import com.zmx.gateway.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.apache.bcel.classfile.Code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * @Description: 登录过滤器, 校验token是否有效
 * @ClassName: MallAuth
 * @Author zhaomxr
 * @Date 2021-11-02 10:23
 */
@Slf4j
@Component
public class MallAuthFilter implements GlobalFilter, Ordered {

    private final static String AUTHORIZATION = "Authorization";
    private final static String COOKIE = "Cookies";
    private final static String PREFIX_TOKEN = "token_";

    private String BLACKLISTCACHE_KEY = "blacklist";
    private String BLACKLISTCACHE_UPDATE_KEY = "blacklist_update";
    private static ResponseResult responseResult;

    private static String requestUrl = "";
    private static String cookies = "";
    private static String token = "";

    @Autowired
    private MallAuthProperties mallAuthProperties;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SendRequestLog sendRequestLogListener;

    @Autowired
    private MemberBlacklistCacheConfig memberBlacklistCacheConfig;

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //request参数
        ServerHttpRequest request = exchange.getRequest();
        responseResult = new ResponseResult(500, "auth check error");

        if (shouldFilter(request)) {
            //直接拒绝
            log.info("用户未登录或登录已经超时过期。");
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JacksonUtil.toJsonString(responseResult).getBytes());
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(bodyDataBuffer));
        }

        //异步 记录所有请求日志
        sendRequestLogListener.send(requestUrl, cookies);

        //通过这个过滤器，进入过滤链中的下一个过滤器
        return chain.filter(exchange);
    }

    private boolean shouldFilter(ServerHttpRequest request) {
        requestUrl = request.getPath().value();
        log.info("type:{} requestUrl: {}", request.getMethod().name(), requestUrl);

        log.info("mallAuthProperties: {}",mallAuthProperties.toString());

        // 判断是否服务内部请求
        String sysCode = request.getHeaders().getFirst("sys_code");
        if (StringUtils.isNotBlank(sysCode)) {
            return false;
        }

        //如果开启指定url忽略过滤
        if (mallAuthProperties.isEnabled()) {
            //如果请求路径与指定拦截url匹配则拦截
            Set<String> interceptUrls = mallAuthProperties.getInterceptUrls();
            if (interceptUrls != null && interceptUrls.size() > 0) {
                for (String interceptUrl : interceptUrls) {
                    if (match(interceptUrl, requestUrl)) {
                        log.info("请求被拦截 interceptUrl: {} requestUrl: {}", interceptUrl, requestUrl);
                        responseResult.setMessage("请求被拦截");
                        return true;
                    }
                }
            }
            //如果请求路径与指定忽略url匹配则跳过
            Set<String> ignoreUrls = mallAuthProperties.getIgnoreUrls();
            for (String ignoreUrl : ignoreUrls) {
                if (match(ignoreUrl, requestUrl)) {
                    return false;
                }
            }
        }

        String authorization = request.getHeaders().getFirst(AUTHORIZATION);
        cookies = request.getHeaders().getFirst(COOKIE);
        log.info("authorization: {}, cookies: {}", authorization, cookies);

        String[] cookieList = cookies.split("; ");
        Map<String, String> cookieMap = new HashMap<String, String>(cookieList.length);
        Arrays.stream(cookieList).forEach(s -> {
            String[] split = s.split("=");
            if (!cookieMap.containsKey(split[0])) {
                cookieMap.put(split[0].trim(), split[1].trim());
            }
        });

        //判断请求头中token是否为空
        if (StringUtils.isEmpty(cookieMap.get("token"))) {
            responseResult.setMessage("token error");
            return true;
        }
        token = cookieMap.get("token").trim();
        //过滤黑名单
        if (filterBlacklist(cookieMap, request)) {
            responseResult.setMessage("当前用户已被封禁");
            return true;
        }
        //校验redis中token
        if (filterRedis(cookieMap)) {
            responseResult.setCode(333);
            responseResult.setMessage("用户已退出登录");
            return true;
        }

        return false;
    }

    private boolean match(String ignoreUrl, String requestUrl) {
        PathMatcher pathMatcher = new AntPathMatcher();
        return pathMatcher.match(ignoreUrl, requestUrl);
    }

    /**
     * 过滤黑名单
     * @param cookieMap
     * @param request
     * @return
     */
    private boolean filterBlacklist(Map<String, String> cookieMap, ServerHttpRequest request){
        //是否开启黑名单
        if (mallAuthProperties.isEnableblacklist()) {
            Map<String, Object> params = new HashMap<String, Object>(2);
            if (mallAuthProperties.isEnableblacklistIp()) {
                params.put("ip", IpAdressUtil.getIpAddress(request));
                if (isBlacklist(params, false, request, cookieMap)) {
                    log.info("ip已加入黑名单: ip: {}", params.get("ip"));
                    return true;
                }
            } else {
                //如果是用户黑名单，直接走redis缓存判断
                if (memberBlacklistCacheConfig.get() != null) {
                    if (memberBlacklistCacheConfig.contain(cookieMap.get("userId"))) {
                        log.info("用户已加入黑名单: userId: {}", cookieMap.get("userId"));
                        return true;
                    }
                } else {
                    //如果走缓存为空，就重新在数据库进行判断
                    params.put("memberId", cookieMap.get("userId"));
                    if (isBlacklist(params, true, request, cookieMap)) {
                        log.info("用户已加入黑名单: userId: {}", cookieMap.get("userId"));
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 是否黑名单
     *
     * @param params
     * @return
     */
    private boolean isBlacklist(Map<String, Object> params, boolean isMemberId, ServerHttpRequest request, Map<String, String> cookieMap) {
        //轮询选举用户服务
        String url = RoundRobinUtil.get(Arrays.asList(mallAuthProperties.getBlacklistUrls().toArray(new String[0])));
        cookies = request.getHeaders().getFirst(COOKIE);
        //构建请求头，服务都会进行鉴权，手动放入Authorization与Cookies
        Map<String, String> headers = new HashMap<String, String>(2);
        headers.put("Authorization", "Bearer " + cookieMap.get("token"));
        headers.put("Cookies", cookies);

        String result = HttpUtil.get(url, params, headers);
        if (StringUtils.isEmpty(result)) {
            return true;
        }
        if (!StringUtils.isEmpty(result)) {
            Map<String, Object> map = JacksonUtil.jsonStringToMap(result);
            //只有是用户memberId黑名单且正确返回数据，才能将当前用户置于黑名单缓存中
            if (Objects.equals(map.get("code"), 200) && map.get("data") != null) {
                if (isMemberId) {
                    //这里可能由于并发引起缓存数据不一致，有些黑名单可能无法及时更新缓存，不影响，最多再次查询数据库后重新更新缓存
                    SimpleBitMap simpleBitMap = new SimpleBitMap(10000);
                    Object object = redisTemplate.opsForValue().get(BLACKLISTCACHE_KEY);
                    if (object != null) {
                        simpleBitMap = (SimpleBitMap) object;
                    }
                    simpleBitMap.add(Integer.valueOf(params.get("memberId").toString()));
                    redisTemplate.opsForValue().set(BLACKLISTCACHE_KEY, simpleBitMap);
                    redisTemplate.opsForValue().set(BLACKLISTCACHE_UPDATE_KEY, true);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 校验redis中token
     * @param cookieMap
     * @return
     */
    private boolean filterRedis(Map<String, String> cookieMap) {
        //过滤redis中用户token
        Object redisToken = redisTemplate.opsForValue().get(PREFIX_TOKEN + cookieMap.get("userId"));
        log.info("redisToken：{} headerTocken: {}", redisToken, token);
        //如果redis中token不存在或者与当前token不相等，则认为当前用户登录已失效
        if (redisToken == null || !Objects.equals(token, redisToken)) {
            return true;
        }
        return false;
    }

    public int getOrder() {
        return 1;
    }
}
